package userHelp

import (
	"fmt"
	"math"
	"math/rand"
	"os"
	"sort"
	"strconv"
	"time"
)

func init() {
	rand.Seed(time.Now().UnixNano())

	// 判定间隔（毫秒），同时也是定时器 tick
	if ms, err := strconv.Atoi(getenv("DECISION_MS", "60000")); err == nil && ms > 0 {
		decisionInterval = time.Duration(ms) * time.Millisecond
	}

	// 强度旋钮
	baseLambdaPerSec = mustParseFloat(getenv("SIM_BASE_LAMBDA", "0.0015")) // 基线 λ（事件/秒）
	simBoost = mustParseFloat(getenv("SIM_BOOST", "2"))                    // 全局倍率
	simDebug = getenv("SIM_DEBUG", "") == "1"                              // 调试打印

	// 钟形分布参数（默认启用）
	bellActive = getenv("BELL_ACTIVE", "1") == "1"          // 1=启用钟形分布；0=走原始分段逻辑
	bellMu = mustParseFloat(getenv("BELL_MU", "35"))        // 钟形中心（靠中间更高）
	bellSigma = mustParseFloat(getenv("BELL_SIGMA", "20"))  // 宽度（越小越尖）
	bellSharp = mustParseFloat(getenv("BELL_SHARP", "1.3")) // 锐化指数（>1更陡，<1更平）

	// 设置吸附用的 SKU 全集（用于鲸鱼尾部或其它吸附）
	if bellActive {
		skuUniverse = append([]int(nil), bellSKUs...)
	} else {
		skuUniverse = unionAndSort(segL.skus, segM.skus, segH.skus, segW.skus)
	}
}

var (
	// ===== 旋钮（可被环境变量覆盖）=====
	decisionInterval = time.Second
	baseLambdaPerSec = 0.0015
	simBoost         = 1.0
	simDebug         = false

	// 钟形分布：两头小、中间大
	bellActive  = true
	bellMu      = 55.0
	bellSigma   = 20.0
	bellSharp   = 1.3
	bellSKUs    = []int{5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 60, 70, 80, 90, 100} // 你的档位（升序）
	skuUniverse []int
)

// ===== 可直接调用：此刻是否发生充值，返回金额；0 表示无人充值 =====
func NextRechargeAmount() int {
	now := time.Now()

	// 非齐次强度（事件/秒）：基线 × 昼夜 × 周内 × 倍率
	lam := baseLambdaPerSec * diurnalMultiplier(now) * weekdayMultiplier(now) * simBoost

	// 在一个判定间隔 Δt 内发生≥1次的概率：p = 1 - exp(-λΔt)
	dt := decisionInterval.Seconds()
	p := 1 - math.Exp(-lam*dt)
	if p > 0.95 {
		p = 0.95
	}
	if simDebug {
		fmt.Printf("debug lam=%.6f/s, dt=%.3fs, p=%.4f\n", lam, dt, p)
	}

	if rand.Float64() < p {
		return sampleAmount() // 金额（单位：元，整数）
	}
	return 0
}

// ===== 连续流：每 interval 输出一次；0 表示无人充值 =====
func StartRechargeStream() (<-chan int, func()) {
	out := make(chan int)
	ticker := time.NewTicker(decisionInterval)
	quit := make(chan struct{})

	go func() {
		defer close(out)
		for {
			select {
			case <-ticker.C:
				out <- NextRechargeAmount()
			case <-quit:
				ticker.Stop()
				return
			}
		}
	}()
	stop := func() { close(quit) }
	return out, stop
}

// ====== 昼夜/周内因子 ======
func diurnalMultiplier(t time.Time) float64 {
	// 双峰余弦：中午、晚间更高；平均约 ~1
	h := float64(t.Hour()) + float64(t.Minute())/60.0
	a1, a2 := 0.35, 0.55
	f := 1 + a1*math.Cos(2*math.Pi*(h-13)/24) + a2*math.Cos(2*math.Pi*(h-20)/24)
	if f < 0.05 {
		f = 0.05 // 不为负
	}
	return f
}
func weekdayMultiplier(t time.Time) float64 {
	switch t.Weekday() {
	case time.Saturday:
		return 1.15
	case time.Sunday:
		return 1.20
	default:
		return 1.00
	}
}

// ====== 钟形分布抽样：两头小、中间大 ======
func bellWeights(skus []int, mu, sigma, sharp float64) []float64 {
	w := make([]float64, len(skus))
	if sigma <= 0 {
		sigma = 1
	}
	twoSigma2 := 2 * sigma * sigma
	var sum float64
	for i, x := range skus {
		d := float64(x) - mu
		wi := math.Exp(-(d * d) / twoSigma2) // 高斯核
		if sharp != 1 {
			wi = math.Pow(wi, sharp) // 锐化/拉平
		}
		if wi < 1e-6 {
			wi = 1e-6 // 留一点最小概率，避免完全抽不到
		}
		w[i] = wi
		sum += wi
	}
	for i := range w {
		w[i] /= sum
	}
	return w
}

func sampleAmountBell() int {
	probs := bellWeights(bellSKUs, bellMu, bellSigma, bellSharp)
	return weightedPick(bellSKUs, probs)
}

// ====== 原分段逻辑（作为后备；bellActive=0 时使用） ======
type segConf struct {
	weight float64
	skus   []int
	probs  []float64
}

var (
	segL = segConf{weight: 0.55, skus: []int{6, 30, 68}, probs: []float64{0.65, 0.30, 0.05}}
	segM = segConf{weight: 0.30, skus: []int{30, 68, 128, 328}, probs: []float64{0.20, 0.50, 0.25, 0.05}}
	segH = segConf{weight: 0.13, skus: []int{68, 128, 328, 648}, probs: []float64{0.10, 0.40, 0.35, 0.15}}
	segW = segConf{weight: 0.02, skus: []int{328, 648}, probs: []float64{0.35, 0.65}}
)

func sampleAmount() int {
	// 启用钟形：直接在 bellSKUs 上抽样
	if bellActive {
		return sampleAmountBell()
	}

	// 否则回退到“分段 + 鲸鱼尾部”
	seg := pickSegment()

	// 鲸鱼有 Lognormal 尾部（并吸附到最近 SKU 档）
	if seg == "W" && rand.Float64() > 0.40 {
		mu, sigma := 5.5, 0.7
		val := math.Exp(mu + sigma*rand.NormFloat64())
		return ceilToNearestSKU(int(math.Round(val)))
	}

	// 其它分段走离散 SKU
	var cfg segConf
	switch seg {
	case "L":
		cfg = segL
	case "M":
		cfg = segM
	case "H":
		cfg = segH
	default:
		cfg = segW
	}
	return weightedPick(cfg.skus, cfg.probs)
}

func pickSegment() string {
	w := []float64{segL.weight, segM.weight, segH.weight, segW.weight}
	switch weightedIndex(w) {
	case 0:
		return "L"
	case 1:
		return "M"
	case 2:
		return "H"
	default:
		return "W"
	}
}

// ====== 工具函数 ======
func weightedIndex(weights []float64) int {
	sum := 0.0
	for _, x := range weights {
		sum += x
	}
	r := rand.Float64() * sum
	acc := 0.0
	for i, x := range weights {
		acc += x
		if r <= acc {
			return i
		}
	}
	return len(weights) - 1
}
func weightedPick(items []int, probs []float64) int {
	sum := 0.0
	for _, p := range probs {
		sum += p
	}
	r := rand.Float64() * sum
	acc := 0.0
	for i, p := range probs {
		acc += p
		if r <= acc {
			return items[i]
		}
	}
	return items[len(items)-1]
}

// 向上吸附到最近不小于 val 的 SKU；超出则取最大 SKU
func ceilToNearestSKU(val int) int {
	for _, sku := range skuUniverse {
		if val <= sku {
			return sku
		}
	}
	return skuUniverse[len(skuUniverse)-1]
}

func unionAndSort(lists ...[]int) []int {
	m := map[int]struct{}{}
	for _, lst := range lists {
		for _, x := range lst {
			m[x] = struct{}{}
		}
	}
	out := make([]int, 0, len(m))
	for x := range m {
		out = append(out, x)
	}
	sort.Ints(out)
	return out
}

// 环境变量工具
func getenv(k, def string) string {
	if v := os.Getenv(k); v != "" {
		return v
	}
	return def
}
func mustParseFloat(s string) float64 {
	f, err := strconv.ParseFloat(s, 64)
	if err != nil {
		return 0
	}
	return f
}
